﻿// 2025/10/15: 首个版本
// 2025/10/17: 选项排序
// 2025/10/22: 支持导入导出加密总线协议文件

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Avalonia.Media;
using ASEva;
using ASEva.UIAvalonia;
using ASEva.Utility;

namespace CommonSource
{
    partial class BusProtocolChooser : DialogPanel
    {
        public BusProtocolChooser() // For designer
        {
            InitializeComponent();
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
        }

        public BusProtocolChooser(object caller, bool selectMode, BusProtocolFileID[] originSelection, String iconResourceName = null)
        {
            InitializeComponent();
            SetResizableMode(400, 350, 800, 450);
            language = new LanguageSwitch(Resources, AgencyLocal.GetAppLanguage());
            Icon = ResourceLoader.Load(iconResourceName)?.ToAvaloniaWindowIcon();
            Title = language["title"];

            model = new BusProtocolChooserViewModel(this, caller, selectMode, originSelection, language);
            model.InitializeItems();
            DataContext = model;

            model.CloseRequest += delegate { Close(); };
        }

        public BusProtocolFileID[] SelectedProtocolFiles => model.Result;

        private LanguageSwitch language;
        private BusProtocolChooserViewModel model;
    }

    partial class BusProtocolChooserViewModel(DialogPanel view, object caller, bool selectMode, BusProtocolFileID[] _originSelection, LanguageSwitch language) : ObservableObject
    {
        // Global settings ///////////////////////////////////////////////////////////////////////////////

        public bool SelectMode => selectMode;

        public bool ClientSide => AgencyLocal.ClientSide;
        
        private BusProtocolFileID[] originSelection = _originSelection ?? [];

        // List box and targets /////////////////////////////////////////////////////////////////////////////////////

        public partial class ProtocolItem(BusProtocolChooserViewModel owner, BusProtocolFileID id, BusProtocolFileState state, bool selected) : ObservableObject
        {
            public BusProtocolFileID ID => id;

            [ObservableProperty]
            [NotifyPropertyChangedFor(nameof(Title))]
            private BusProtocolFileState state = state;

            public String Title
            {
                get
                {
                    String result = id.FileName;
                    if (State != BusProtocolFileState.OK) result += " (ERROR)";
                    if (selected) result += " (SELECTED)";
                    return result;
                }
            }

            public bool IsEnabled => !selected;

            [ObservableProperty]
            private bool isChecked = false;

            partial void OnIsCheckedChanged(bool value)
            {
                owner.OnBusProtocolChooserItemCheckedChanged();
            }
        }

        public async void InitializeItems()
        {
            var list = (await AgencyAsync.GetBusProtocolFileIDList())?.ToList();
            if (list == null) return;

            list.Sort((a, b) => a.FileName.CompareTo(b.FileName));
            var idList = list.ToArray();

            var tasks = new Task<BusProtocolFileState>[idList.Length].Populate(i => AgencyAsync.GetBusProtocolFileState(idList[i]));
            var stateList = await Task.WhenAll(tasks);

            var items = new ProtocolItem[idList.Length].Populate(i => new ProtocolItem(this, idList[i], stateList[i], originSelection.Contains(idList[i])));
            foreach (var item in items) ProtocolItems.Add(item);
        }

        [ObservableProperty]
        private ObservableCollection<ProtocolItem> protocolItems = new();

        [ObservableProperty]
        [NotifyPropertyChangedFor(nameof(FilePath))]
        [NotifyPropertyChangedFor(nameof(FileMD5))]
        [NotifyPropertyChangedFor(nameof(FilePathForeground))]
        [NotifyPropertyChangedFor(nameof(FileMD5Foreground))]
        private ProtocolItem informationTarget = null;

        partial void OnInformationTargetChanged(ProtocolItem value)
        {
            if (SelectMode) return;
            OperationTargets = value != null ? [value] : [];
        }

        public void OnBusProtocolChooserItemCheckedChanged()
        {
            var list = new List<ProtocolItem>();
            foreach (var item in ProtocolItems)
            {
                if (item.IsChecked) list.Add(item);
            }
            OperationTargets = list.ToArray();
        }

        [ObservableProperty]
        [NotifyPropertyChangedFor(nameof(CanRemove))]
        [NotifyPropertyChangedFor(nameof(CanRedirect))]
        [NotifyPropertyChangedFor(nameof(CanSelect))]
        [NotifyPropertyChangedFor(nameof(CanExportEncrypted))]
        private ProtocolItem[] operationTargets = [];

        public BusProtocolFileID[] Result { get; private set; } = [];

        // Buttons ///////////////////////////////////////////////////////////////////////////////////////

        public bool CanRemove
        {
            get
            {
                if (OperationTargets.Length >= 1) return !OperationTargets.ToList().Exists(item => item.State == BusProtocolFileState.OK && originSelection.Contains(item.ID));
                else return false;
            }
        }

        public bool CanRedirect
        {
            get
            {
                return OperationTargets.Length == 1 && OperationTargets[0].State != BusProtocolFileState.OK;
            }
        }

        public bool CanSelect
        {
            get
            {
                if (OperationTargets.Length >= 1) return !OperationTargets.ToList().Exists(item => item.State != BusProtocolFileState.OK || originSelection.Contains(item.ID));
                else return false;
            }
        }

        public bool CanExportEncrypted
        {
            get
            {
                return OperationTargets.Length == 1 && OperationTargets[0].State == BusProtocolFileState.OK;
            }
        }

        [RelayCommand]
        private async Task AddProtocol()
        {
            var selected = await App.ShowOpenFileDialog(view, language["dialog-title"], true, null, new()
            {
                [language["suffix-all"]] = [".dbc", ".arxml", ".xml", ".sef"],
                [language["suffix-dbc"]] = [".dbc"],
                [language["suffix-arxml"]] = [".arxml"],
                [language["suffix-fibex"]] = [".xml"],
                [language["suffix-sef"]] = [".sef"],
            });
            if (selected == null) return;

            foreach (var file in selected)
            {
                BusProtocolFileID[] added = null;
                var result = AgencyLocal.AddBusProtocolFile(file, out added);
                ProtocolItem finalItem = null;
                switch (result)
                {
                    case AddBusProtocolResult.AlreadyAdded:
                        await AgencyLocal.PopupNotice(language.Format("already-added", file));
                        break;
                    case AddBusProtocolResult.CalculateMD5Failed:
                        var row1 = language.Format("md5-failed-row1", file);
                        var row2 = language.Format("md5-failed-row2"); // Occurs mainly in Windows, here's the solution / 主要在Windows下出现，解决方法：HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy->enable=0
                        await AgencyLocal.PopupError(row1 + "\n" + row2);
                        break;
                    case AddBusProtocolResult.OK:
                        foreach (var item in added)
                        {
                            finalItem = new ProtocolItem(this, item, BusProtocolFileState.OK, false);
                            ProtocolItems.Add(finalItem);
                        }
                        break;
                }
                if (finalItem != null) InformationTarget = finalItem;
            }
        }

        [RelayCommand]
        private void RemoveProtocol()
        {
            foreach (var target in OperationTargets)
            {
                if (InformationTarget != null && InformationTarget.Equals(target)) InformationTarget = null;
                ProtocolItems.Remove(target);
                AgencyLocal.RemoveBusProtocolFile(target.ID);
            }
            OperationTargets = [];
        }

        [RelayCommand]
        private async Task RedirectProtocol()
        {
            var target = OperationTargets[0];
            var selected = target.ID;
            if (selected.FileName.Contains("@"))
            {
                await AgencyLocal.PopupError(language["channel-unsupported"]);
                return;
            }

            var selectedPaths = await App.ShowOpenFileDialog(view, language["dialog-title"], false, null, new()
            {
                [language["suffix-dbc"]] = [".dbc"],
                [language["suffix-arxml"]] = [".arxml"],
                [language["suffix-fibex"]] = [".xml"],
                [language["suffix-sef"]] = [".sef"],
            });
            if (selectedPaths == null || selectedPaths.Length == 0) return;

            var file = selectedPaths[0];
            if (!AgencyLocal.UpdateBusProtocolFilePath(selected, file))
            {
                await AgencyLocal.PopupError(language["md5-mismatch"]);
                return;
            }

            target.State = BusProtocolFileState.OK;
            InformationTarget = null;
            InformationTarget = target;
        }

        [RelayCommand]
        private async Task ExportEncrypted()
        {
            var target = OperationTargets[0];
            var selected = target.ID;
            var selectedFolder = await App.ShowSelectFolderDialog(view, language["export-dialog-title"], Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));
            if (selectedFolder == null) return;

            var xml = Xml.Create("root");
            xml.DocumentElement.Attributes.Append(xml.CreateAttribute("file_id")).Value = selected.FileName;
            xml.DocumentElement.Attributes.Append(xml.CreateAttribute("folder_path")).Value = selectedFolder;
            await AgencyAsync.RunStandaloneTask(caller, "export-bus-protocol-encrypted.system", xml.InnerXml);
        }
        
        [RelayCommand]
        private void Select()
        {
            Result = OperationTargets.Select(item => item.ID).ToArray();
            CloseRequest?.Invoke(this, EventArgs.Empty);
        }
        
        [RelayCommand]
        private void Cancel()
        {
            CloseRequest?.Invoke(this, EventArgs.Empty);
        }

        public event EventHandler CloseRequest;

        // Information ///////////////////////////////////////////////////////////////////////////////////

        public String FilePath
        {
            get
            {
                if (InformationTarget != null && !ClientSide)
                {
                    return AgencyLocal.GetBusProtocolFilePath(InformationTarget.ID);
                }
                else return "---";
            }
        }

        public String FileMD5
        {
            get
            {
                if (InformationTarget != null)
                {
                    return InformationTarget.ID.MD5;
                }
                else return "---";
            }
        }

        public IBrush FilePathForeground
        {
            get
            {
                if (InformationTarget != null && InformationTarget.State == BusProtocolFileState.FileNotExist) return Brushes.Red;
                else return App.DarkTheme ? Brushes.LightGray : Brushes.Black;
            }
        }

        public IBrush FileMD5Foreground
        {
            get
            {
                if (InformationTarget != null && InformationTarget.State == BusProtocolFileState.MD5NotCorrect) return Brushes.Red;
                else return App.DarkTheme ? Brushes.LightGray : Brushes.Black;
            }
        }
    }
}